#include "TestCMA.h"

#include "../../Learning/SingleSampleOptimizationSupervisor.h"
#include "../../Learning/ArrayOptimizationSupervisor.h"
#include "../../Learning/MultilayerPerceptron.h"

#include "ReClaM/CMAOptimizer.h"
#include "ReClaM/MSEFFNet.h"
#include "ReClaM/createConnectionMatrix.h"
#include "../common/TestUtils.h"
#include <memory>
#include <vector>
#include "../../clUtils.h"
#include "../../Learning/CMAOptimizer.h"
#include "Rng/GlobalRng.h"

CPPUNIT_TEST_SUITE_REGISTRATION(TestCMA);

using namespace std;

TestCMA::TestCMA() {
}

TestCMA::~TestCMA() {
}

void TestCMA::setUp() {
}

void TestCMA::tearDown() {
}

cl_device_id getCPUDevice() {
	vector<cl_device_id> devices;
	enumOpenCLDevices(devices, CL_DEVICE_TYPE_CPU);
	return devices[1];
}

cl_device_id getGPUDevice() {
    vector<cl_device_id> devices;
    enumOpenCLDevices(devices, CL_DEVICE_TYPE_GPU);
    return devices[0];
}

void fillWithRandomData(learn::ArrayOptimizationSupervisor &outProvider, 
		Array<double> &inputs, Array<double> &outputs, int sampleCount) 
{
	int inputCount = outProvider.getInputLength();
	int outputCount = outProvider.getTargetLength();
	vector<float> floatInputs(inputCount);
	vector<float> floatOutputs(outputCount);
	
	inputs.resize(sampleCount, inputCount, false);
	outputs.resize(sampleCount, outputCount, false);
	
	for (int i = 0; i < sampleCount; i++) {
		
		for (int j = 0; j < inputCount; j++) {			
			inputs(i, j) = floatInputs[j] = getRandom(-1, 1);
		}
		
		for (int j = 0; j < outputCount; j++) {
			outputs(i, j) = floatOutputs[j] = getRandom(-1, 1);
		}
		
		outProvider.addSample(&floatInputs[0], &floatOutputs[0]);
	}
}

void TestCMA::testMethod() {
	
//	int inputCount = 5;	
//	int hiddenCount = 5;
//	int outputCount = 3;
//	 
//	int sampleCount = 10;
//	int numIterations = 5;
//		
//	Array<int> connections;
//	createConnectionMatrix(connections, inputCount, hiddenCount, outputCount, true, false, false, true);
//	MSEFFNet sharkNet = MSEBFFNet(inputCount, outputCount, connections);
//	int numParams = sharkNet.getParameterDimension();
//				
//	srand(123456);
//	
//	// randomize weights	
//	for (int i = 0; i < numParams; i++) {
//		sharkNet.setParameter(i, getRandom());
//	}
//	
//	// fill supervisor's data
//	Array<double> inputs = Array<double>(sampleCount, inputCount);
//	Array<double> targets = Array<double>(sampleCount, outputCount);
//		
//	for (int i = 0; i < sampleCount; i++) {		
//		for (int j = 0; j < inputCount; j++) {			
//			inputs(i, j) = getRandom(-1, 1);
//		}
//		
//		for (int j = 0; j < outputCount; j++) {
//			targets(i, j) = getRandom(-1, 1);
//		}		
//	}
//	
//	// optimize
//	CMAOptimizer optimizer;
//	optimizer.init(sharkNet);
//	
//	for (int i = 0; i < numIterations; i++) {
//		optimizer.optimize(sharkNet, sharkNet, inputs, targets);
//	}	
	
	CPPUNIT_ASSERT(true);
}

void TestCMA::compareCMA() {
	 cl_command_queue queue = createQueue(getCPUDevice(), CL_QUEUE_PROFILING_ENABLE | CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE);
	
	double eps = 1E-5;
	int inputCount = 5;
	int outputCount = 3;
	int hiddenCount = 5;
	
	// create nets
	Array<int> connections;
	createConnectionMatrix(connections, inputCount, hiddenCount, outputCount, true, false, false, true);
	MSEFFNet sharkNet = MSEBFFNet(inputCount, outputCount, connections);
	int numParams = sharkNet.getParameterDimension();
		
	learn::MultilayerPerceptron::Builder builder;
	auto_ptr<learn::MultilayerPerceptron> net = auto_ptr<learn::MultilayerPerceptron>(builder
			.setInputCount(inputCount)
			.addHiddenLayer(hiddenCount)
			.addHiddenLayer(outputCount)
			.create(queue));
		
	// randomize parameters
	vector<float> params(numParams);
	for (int i = 0; i < numParams; i++) {
		sharkNet.setParameter(i, params[i] = getRandom(-0.1f, 0.1f));
	}
	net->setParameters(&params[0], 0, numParams);
	
	// test learning
	int dataSize = 50;
	learn::ArrayOptimizationSupervisor trainData(inputCount, outputCount);
	Array<double> sharkInputs = Array<double>(0, 0);
	Array<double> sharkTargets = Array<double>(0, 0);		
	fillWithRandomData(trainData, sharkInputs, sharkTargets, dataSize);	
	
	learn::CMAOprimizer myOptimizer;
	myOptimizer.init(*net);
	learn::normalRandom.seed(1234567);
	
	CMAOptimizer sharkOptimizer;	
	sharkOptimizer.init(sharkNet);
	Rng::gauss.seed(1234567);
	
	float e1;
	double e2;
	for (int i = 0; i < 5; i++) {
		e1 = myOptimizer.optimize(*net, *net, trainData);
		e2 = sharkOptimizer.optimize(sharkNet, sharkNet, sharkInputs, sharkTargets);
		e1 *= e2;
	}
}

